
const PENDING = 'pending'
const FULFILLED = 'fulfilled'
const REJECTED = 'rejected'

function Promise_(executor) {

  /**
   * promiseA+ 2.1.1
   */
  let self = this
  self.status = PENDING
  self.onFulfilled = []
  self.onRejected = []
  // self.value = null // resolve
  // self.reason = null // reject
  
  /**
   * promiseA+ 2.1 
   */
  function resolve(value) {
    if (self.status === PENDING) {
      self.status = FULFILLED
      self.value = value
      /**
       * PromiseA+ 2.2.6.1
       * @description f/when promise is fulfilled, all respective onFulfilled callbacks must execute in the order of their originating calls to then.
       */
      self.onFulfilled.forEach(fn => fn())
    }
  }

  function reject(reason) {
    if (self.status === PENDING) {
      self.status = REJECTED
      self.reason = reason
      /**
       * PromiseA+ 2.2.6.2
       * @description If/when promise is rejected, all respective onRejected callbacks must execute in the order of their originating calls to then.
       */
      self.onRejected.forEach(fn => fn())
    }
  }


  try {
    executor(resolve, reject)
  } catch (e) {
    reject(e)
  }
}

Promise_.prototype.then = function(onFulfilled, onRejected){
    /**
     * promiseA+ 2.2.1 Both onFulfilled and onRejected are optional arguments
     * PromiseA+ 2.2.5 onFulfilled and onRejected must be called as functions
     * PromiseA+ 2.2.7.3 If onFulfilled is not a function and promise1 is fulfilled, promise2 must be fulfilled with the same value as promise1.
     * PromiseA+ 2.2.7.4 If onRejected is not a function and promise1 is rejected, promise2 must be rejected with the same reason as promise1
     */
    onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value;
    onRejected = typeof onRejected === 'function' ? onRejected : reason => {throw reason}

    let self = this;

    let promise2 = new Promise_((resolve, reject) => {
      // switch(self.status) {
        if(self.status === FULFILLED) {

          /**
           * PromiseA+ 2.2.4 onFulfilled or onRejected must not be called until the execution context stack contains only platform code [microtask queue]
           */
           setTimeout(() => {
            /**
             * PromiseA+ 2.2.2 If onFulfilled is a function:
             *  2.2.2.1 it must be called after promise is fulfilled, with promise’s value as its first argument.
             *  2.2.2.2 it must not be called before promise is fulfilled.
             *  2.2.2.3 it must not be called more than once.
             */
            try {
              /**
               * PromiseA+ 2.2.7.1 If either onFulfilled or onRejected returns a value x, run the Promise Resolution Procedure [[Resolve]](promise2, x).
               */
              let x = onFulfilled(self.value)
              resolvePromise(promise2, x, resolve, reject)
            } catch(e) {
              /**
               * PromiseA+ 2.2.7.2 If either onFulfilled or onRejected throws an exception e, promise2 must be rejected with e as the reason.
               */
              reject(e)
            }
           })
        } else if (self.status === REJECTED) {
          /**
           * PromiseA+ 2.2.3 If onRejected is a function
           *  2.2.3.1 it must be called after promise is rejected, with promise’s reason as its first argument.
           *  2.2.3.2 it must not be called before promise is rejected.
           *  2.2.3.3 it must not be called more than once.
           */
          setTimeout(() => {
            try {
              /**
               * PromiseA+ 2.2.7.1 If either onFulfilled or onRejected returns a value x, run the Promise Resolution Procedure [[Resolve]](promise2, x).
               */
              let x = onRejected(self.reason)
              resolvePromise(promise2, x, resolve, reject)
            } catch (e) {
              /**
               * PromiseA+ 2.2.7.2 If either onFulfilled or onRejected throws an exception e, promise2 must be rejected with e as the reason.
               */
              reject(e)
            }
          })
        } else if (self.status === PENDING) {
          self.onFulfilled.push(() => {
            setTimeout(() => {
              try {
                let x = onFulfilled(self.value)
                resolvePromise(promise2, x, resolve, reject)
              } catch(e) {
                reject(e)
              }
            })
          })

          self.onRejected.push(() => {
            setTimeout(() => {
              try {
                let x = onRejected(self.reason)
                resolvePromise(promise2, x, resolve, reject)
              } catch(e) {
                reject(e)
              }
            })
          })
        }
      })
    return promise2
  }
  

  function resolvePromise(promise2, x, resolve, reject) {
    let self = this;

    /**
     * PromiseA+ 2.3.1 If promise and x refer to the same object, reject promise with a TypeError as the reason.
     */
    if (promise2 === x) {
      reject(new TypeError('Chaining cycle'))
    }
    if (x && typeof x === 'object' || typeof x === 'function') {
      /**
       * PromiseA+ 2.3.3.3 If then is a function, call it with x as this, first argument resolvePromise, and second argument rejectPromise
       */
      let used;
      try {
        let then = x.then;
        if (typeof then === 'function') {
          then.call(x, (y) => {
            /**
             * PromiseA+ 2.3.3.3.1 If/when resolvePromise is called with a value y, run [[Resolve]](promise, y).
             */
            if (used) return;
            used = true;
            resolvePromise(promise2, y, resolve, reject)
          }, (r) => {
            /**
             * PromiseA+ 2.3.3.3.2 If/when rejectPromise is called with a reason r, reject promise with r.
             */
            if (used) return;
            used = true
            reject(r);
            // resolvePromise(promise2, r, resolve, reject)
          })
        } else {
          /**
           * PromiseA+ 2.3.3.4 If then is not a function, fulfill promise with x.
           */
          if (used) return;
          used = true;
          resolve(x)
        }
      } catch(e) {
        /**
         * PromiseA+ 2.3.3.2 If retrieving the property x.then results in a thrown exception e, reject promise with e as the reason.
         */
        if (used) return;
        used = true;
        reject(e)
      }
    } else {
      /**
       * PromiseA+ 2.3.3.4 If then is not a function, fulfill promise with x.
       */
      resolve(x)
    }
  }

  class PromiseKB{
  
  }
  PromiseKB.defer = PromiseKB.deferred = function(){
    let dfd = {};
    dfd.promise = new Promise_((resolve, reject)=>{
      dfd.resolve = resolve;
      dfd.reject = reject;
    });
    return dfd;
  }
  module.exports =  PromiseKB